<?php

namespace DDTrace\Bridge;

/**
 * Tells whether or not tracing is enabled without having to fire the auto-loading mechanism.
 *
 * @return bool
 */
function dd_tracing_enabled()
{
    if ('cli' === PHP_SAPI) {
        return dd_env_as_boolean('DD_TRACE_CLI_ENABLED', false);
    }

    return dd_env_as_boolean('DD_TRACE_ENABLED', true);
}

/**
 * Returns the boolean value of an environment variable:
 *  - if NOT defined then returns $default
 *  - if defined and equals (case-insensitive) to 'true' or '1' then returns true
 *  - if defined and equals (case-insensitive) to 'false' or '0' then returns false
 *  - otherwise returns $default
 *
 * @param string $name
 * @param boolean $default
 * @return bool
 */
function dd_env_as_boolean($name, $default)
{
    $envValue = getenv($name);
    if ($envValue === false) {
        return $default;
    }

    $envValue = strtolower(trim($envValue));
    if ('true' === $envValue || '1' === $envValue) {
        return true;
    } elseif ('false' === $envValue || '0' === $envValue) {
        return false;
    } else {
        return $default;
    }
}

/**
 * Extracts an array ['My\Autoloader\Class', 'method'] if the loader class and methods are in a known format, otherwise
 * it returns null.
 *
 * @param \callable $loader As in http://php.net/manual/en/language.types.callable.php
 * @return array|null
 */
function extract_autoloader_class_and_method($loader)
{
    // Covers case: spl_autoloader_register('Some\Class::load')
    if (is_string($loader)) {
        $parts = explode('::', $loader);
        return count($parts) === 2 ? [$parts[0], $parts[1]] : null;
    }
    // Covers case: spl_autoloader_register(['Some\Class', 'load'])
    if (is_array($loader) && count($loader) === 2) {
        if (is_string($loader[0])) {
            return [$loader[0], $loader[1]];
        } elseif (is_object($loader[0])) {
            return [get_class($loader[0]), $loader[1]];
        } else {
            return null;
        }
    }
    // Case not covered: spl_autoloader_register(null);
    // Case not covered: spl_autoloader_register(function () {});
    return null;
}

/**
 * Registers the Datadog.
 */
function dd_register_autoloader()
{
    require_once __DIR__ . '/dd_required_deps_autoloader.php';
    require_once __DIR__ . '/dd_optional_deps_autoloader.php';

    spl_autoload_register(['\DDTrace\Bridge\OptionalDepsAutoloader', 'load'], true, true);
    spl_autoload_register(['\DDTrace\Bridge\RequiredDepsAutoloader', 'load'], true, true);
}

/**
 * Unregisters the Datadog.
 */
function dd_unregister_autoloader()
{
    spl_autoload_unregister(['\DDTrace\Bridge\RequiredDepsAutoloader', 'load']);
    spl_autoload_unregister(['\DDTrace\Bridge\OptionalDepsAutoloader', 'load']);
}

/**
 * Traces spl_autoload_register in order to provide hooks for auto-instrumentation to be executed.
 */
function dd_wrap_autoloader()
{
    dd_register_autoloader();

    /* CodeIgniter v2 does not use an autoloader. Tracing the CI_Hooks
     * constructor let's us set up the world because it is called very early
     * in CodeIgniter's startup process before we need to trace anything.
     *
     * Note that this hook cannot use dd_trace_method, since dd_trace_method
     * will attempt to create a span before everything we need to make spans
     * has been set up, it puts us in a bad state.
     */
    \dd_trace('CI_Hooks', '__construct', function () {
        require __DIR__ . '/dd_init.php';
        // Since the above line initializes ddtrace, we don't need the autoloader to do it
        \dd_untrace('spl_autoload_register');
        return \dd_trace_forward_call();
    });

    // User app is not using any autoloader we just import the initialization script
    if (dd_env_as_boolean('DD_TRACE_NO_AUTOLOADER', false)) {
        require __DIR__ . '/dd_init.php';
        return;
    }

    // Composer auto-generates a class loader with a varying name which follows the pattern
    // `ComposerAutoloaderInitaa9e6eaaeccc2dd24059c64bd3ff094c`. The name of this class varies and this variable is
    // used to keep track of the actual name.
    $composerAutogeneratedClass = null;

    dd_trace('spl_autoload_register', function () use (&$composerAutogeneratedClass) {
        $originalAutoloaderRegistered = dd_trace_forward_call();
        $args = func_get_args();
        if (sizeof($args) == 0) {
            return $originalAutoloaderRegistered;
        }
        list($loader) = $args;

        $extractedClassAndMethod = extract_autoloader_class_and_method($loader);
        if (empty($extractedClassAndMethod)) {
            return $originalAutoloaderRegistered;
        }
        list ($loaderClass) = $extractedClassAndMethod;

        // If we detect the composer autogenerated autoloader, there is nothing we have to do at this time.
        // We wait for the next class, which is the actual composer autoloader.
        // Composer registers its own autoloader pre-pending it to the list of already
        // registered auto-loaders. For this reason it would load the DDTrace namespace from its vendor folder
        // if available. On the other hand, returning when we detect a `ComposerAutoloaderInit*` class is required
        // otherwise we would trigger auto-instrumentation before the actual composer autoloader kicks in and we would
        // always use the DDTrace classes provided with the bundle even if the user declared `datadog/dd-trace` in his
        // composer.
        $generatedComposerClassPrefix = 'ComposerAutoloaderInit';
        if (substr($loaderClass, 0, strlen($generatedComposerClassPrefix)) === $generatedComposerClassPrefix) {
            $composerAutogeneratedClass = $loaderClass;
            return $originalAutoloaderRegistered;
        }

        dd_untrace('spl_autoload_register');
        require __DIR__ . '/dd_init.php';
    });
}
